#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <fcntl.h>
#include <string.h>

#ifdef NON_OS

#else
#include <linux/fb.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <errno.h>
#endif

#include <config.h>

#ifdef GPU_SUPPORT

#include <systems.h>
#include <types.h>
#include <drivers/graphic/glt.h>
#include <drivers/graphic/gpu.h>
#include <gapi.h>
#include <misc.h>

extern int xres, yres, bpp, fbsize, fb;
extern char *screen_base;
extern s8 glt_layer_idx_counter;
extern BOOLEAN defer_gobj_by_page;

inline void gpu_set_lcd_vert(u16 tvs, u16 tvp, u16 tve)
{
    u32 v = tvs << 20 | tvp << 10 | tve;
    ioctl(fb, FBIOCTL_SET_LCDV, &v);
}

inline void gpu_set_lcd_hori(u16 ths, u16 thp, u16 the)
{
    u32 h = ths << 20 | thp << 10 | the;
    ioctl(fb, FBIOCTL_SET_LCDH, &h);
}

inline void gpu_set_lcd_res(u16 xr, u16 yr)
{
    u32 r = yr << 10 | xr;
    ioctl(fb, FBIOCTL_SET_LCDRES, &r);
}

inline void gpu_set_tp_rate(u8 rate)
{
    u8 data = rate;
    ioctl(fb, FBIOCTL_SET_TPRATE, &data);
}

inline void gpu_set_tp_value(COLOR c)
{
    //DPRINTF("Setting TP Value: (R)%d, (G)%d, (B)%d\n", c.r, c.g, c.b);
    u16 color = RGB2PIXEL565(c.r, c.g, c.b);
    ioctl(fb, FBIOCTL_SET_TPVALUE, &color);
}

inline char *gpu_get_fb_base(void)
{
    u32 base;
    ioctl(fb, FBIOCTL_GET_FBBASE0, &base);
    return (char *)base;
}

void gpu_flush(s8 index, u8 cmd)
{
    GLT glt_reg;  /* 20070401 */
    
    if(index >= 0) {
        gpu_wait_busy(true);
        /* Flush all data in GLT to specified framebuffer area */
        glt_read(index, &glt_reg);
        glt_reg.end_layer = 1;
        glt_write(index, &glt_reg);
        
        switch(cmd) {
            case REG_COMMAND_WRDISP: gpu_write_display(fb); break;
            case REG_COMMAND_WRBUF: gpu_write_buffer(fb, false); break;
            case REG_COMMAND_WRBUFSW: gpu_write_buffer(fb, true); break;
        }
        
        /* Wait until GPU finishing it's job */
        gpu_wait_busy(true);
    }
    else if(cmd == REG_COMMAND_WRBUFSW) {
        gpu_switch_page();
    }
  
    glt_reset_index();
}


int gpu_draw_dot(DOT *dot)
{
    GLT glt_reg;
    
    if(fb <= 0) {
        ERROR("Framebuffer device is not ready!\n");
        return -1;
    }
    
    if(!dot)
        return -2;
        
    glt_index_inc();
    
    if(glt_layer_idx_counter >= MAX_LAYER_INDEX) {
        //DPRINTF("layer index out of bound\n");
        if(defer_gobj_by_page)
            gpu_flush(glt_layer_idx_counter - 1, REG_COMMAND_WRBUF);
        else
            gpu_flush(glt_layer_idx_counter - 1, dot->hw_dep_prop.command);
    }
    
    memset(&glt_reg, 0x0, sizeof(GLT));
    glt_reg.img_src_addr = (u32)RGB2PIXEL565(dot->color->r, dot->color->g, dot->color->b);
    glt_reg.height = dot->size;
    glt_reg.width = dot->size;
    //glt_reg.center_x = 0;
    //glt_reg.center_y = 0;
    glt_reg.img_x = dot->x;
    glt_reg.img_y = dot->y;
    glt_reg.draw = GLT_DRAW_RECT_FILLED;
    glt_reg.scale = dot->hw_dep_prop.scale;
    glt_reg.transparent = dot->tp;
    glt_reg.angle = dot->hw_dep_prop.angle;
    
    if(glt_layer_idx_counter < MAX_LAYER_INDEX)
        glt_reg.end_layer = dot->hw_dep_prop.end_layer;
    else
        glt_reg.end_layer = true;
    
    gpu_wait_busy(true);
    glt_write(glt_layer_idx_counter, &glt_reg);
      
    if(glt_reg.end_layer) {
        switch(dot->hw_dep_prop.command) {
            case REG_COMMAND_WRDISP: gpu_write_display(fb); break;
            case REG_COMMAND_WRBUF: gpu_write_buffer(fb, false); break;
            case REG_COMMAND_WRBUFSW: gpu_write_buffer(fb, true); break;
        }
        glt_reset_index();
    }
    
    return 0;
}


int gpu_draw_line(LINE *line)
{
    GLT glt_reg;
    double slope;
    int height;
    int width;
    u8 direction;
    
    if(fb <= 0) {
        ERROR("Framebuffer device is not ready!\n");
        return -1;
    }
    
    if(!line)
        return -2;
        
    glt_index_inc();
    
    if(glt_layer_idx_counter >= MAX_LAYER_INDEX) {
        //DPRINTF("layer index out of bound\n");
        if(defer_gobj_by_page)
            gpu_flush(glt_layer_idx_counter - 1, REG_COMMAND_WRBUF);
        else
            gpu_flush(glt_layer_idx_counter - 1, line->hw_dep_prop.command);
    }
    
    width = abs(line->dx - line->sx);
    height = abs(line->dy - line->sy);
    
    if(width > height)
        slope = (double)height/(double)width;
    else
        slope = (double)width/(double)height;
    
    if(line->dx < line->sx && line->dy > line->sy)
        direction = LINE_DIRECTION_LOWER_LEFT;
    else if(line->dx > line->sx && line->dy < line->sy)
        direction = LINE_DIRECTION_UPPER_RIGHT;
    else if(line->dx < line->sx && line->dy < line->sy)
        direction = LINE_DIRECTION_UPPER_LEFT;
    else
        direction = LINE_DIRECTION_LOWER_RIGHT;
    
    memset(&glt_reg, 0x0, sizeof(GLT));
    glt_reg.img_src_addr = (u32)RGB2PIXEL565(line->color->r, line->color->g, line->color->b);
    glt_reg.height = height;
    glt_reg.width = width;
    //glt_reg.center_x = 0;
    //glt_reg.center_y = 0;
    glt_reg.slope = (int)slope << 9; /* slope *= 512 */;
    glt_reg.img_x = line->sx;
    glt_reg.img_y = line->sy;
    glt_reg.direction = direction;
    glt_reg.draw = GLT_DRAW_LINE;
    glt_reg.scale = line->hw_dep_prop.scale;
    glt_reg.transparent = line->tp;
    glt_reg.angle = line->hw_dep_prop.angle;
    if(glt_layer_idx_counter < MAX_LAYER_INDEX)
        glt_reg.end_layer = line->hw_dep_prop.end_layer;
    else
        glt_reg.end_layer = true;
    
    gpu_wait_busy(true);
    glt_write(glt_layer_idx_counter, &glt_reg);
      
    if(glt_reg.end_layer) {
        switch(line->hw_dep_prop.command) {
            case REG_COMMAND_WRDISP: gpu_write_display(fb); break;
            case REG_COMMAND_WRBUF: gpu_write_buffer(fb, false); break;
            case REG_COMMAND_WRBUFSW: gpu_write_buffer(fb, true); break;
        }
            
        glt_reset_index();
    }
        
    return 0;
}


int gpu_draw_rect(RECT *rect)
{
    GLT glt_reg;
    
    if(fb <= 0) {
        ERROR("Framebuffer device is not ready!\n");
        return -1;
    }
    
    if(!rect)
        return -2;
        
    glt_index_inc();
    
    if(glt_layer_idx_counter >= MAX_LAYER_INDEX) {
        //DPRINTF("layer index out of bound\n");
        if(defer_gobj_by_page)
            gpu_flush(glt_layer_idx_counter - 1, REG_COMMAND_WRBUF);
        else
            gpu_flush(glt_layer_idx_counter - 1, rect->hw_dep_prop.command);
    }
    
    memset(&glt_reg, 0x0, sizeof(GLT));
    glt_reg.img_src_addr = (u32)RGB2PIXEL565(rect->color->r, rect->color->g, rect->color->b);
    glt_reg.height = rect->height;
    glt_reg.width = rect->width;
    //glt_reg.center_x = 0;
    //glt_reg.center_y = 0;
    glt_reg.img_x = rect->x;
    glt_reg.img_y = rect->y;
    
    if(rect->filled)
        glt_reg.draw = GLT_DRAW_RECT_FILLED;
    else
        glt_reg.draw = GLT_DRAW_RECT_FRAME;
        
    glt_reg.scale = rect->hw_dep_prop.scale;
    glt_reg.transparent = rect->tp;
    glt_reg.angle = rect->hw_dep_prop.angle;
    
    if(glt_layer_idx_counter < MAX_LAYER_INDEX)
        glt_reg.end_layer = rect->hw_dep_prop.end_layer;
    else
        glt_reg.end_layer = true;
    
    gpu_wait_busy(true);
    glt_write(glt_layer_idx_counter, &glt_reg);
    
    if(glt_reg.end_layer) {
        switch(rect->hw_dep_prop.command) {
            case REG_COMMAND_WRDISP: gpu_write_display(fb); break;
            case REG_COMMAND_WRBUF: gpu_write_buffer(fb, false); break;
            case REG_COMMAND_WRBUFSW: gpu_write_buffer(fb, true); break;
        }
            
        glt_reset_index();
    }
        
    return 0;
}


int gpu_draw_image(IMAGE *image)
{
    GLT glt_reg;
    
    if(fb <= 0) {
        ERROR("Framebuffer device is not ready!\n");
        return -1;
    }
    
    if(!image)
        return -2;
        
    glt_index_inc();
    
    if(glt_layer_idx_counter >= MAX_LAYER_INDEX) {
        //DPRINTF("layer index out of bound\n");
        if(defer_gobj_by_page)
            gpu_flush(glt_layer_idx_counter - 1, REG_COMMAND_WRBUF);
        else
            gpu_flush(glt_layer_idx_counter - 1, image->hw_dep_prop.command);
    }
    
    memset(&glt_reg, 0x0, sizeof(GLT));
    glt_reg.img_src_addr = (u32)image->data;
    glt_reg.height = image->height;
    glt_reg.width = image->width;
    //glt_reg.center_x = 0;
    //glt_reg.center_y = 0;
    glt_reg.img_x = image->x;
    glt_reg.img_y = image->y;
    glt_reg.draw = GLT_DRAW_IMAGE;
    glt_reg.scale = image->hw_dep_prop.scale;
    glt_reg.transparent = image->tp;
    glt_reg.angle = image->hw_dep_prop.angle;
    if(glt_layer_idx_counter < MAX_LAYER_INDEX)
        glt_reg.end_layer = image->hw_dep_prop.end_layer;
    else
        glt_reg.end_layer = true;
    
    gpu_wait_busy(true);
    glt_write(glt_layer_idx_counter, &glt_reg);
    
    if(glt_reg.end_layer) {
        switch(image->hw_dep_prop.command) {
            case REG_COMMAND_WRDISP: gpu_write_display(fb); break;
            case REG_COMMAND_WRBUF: gpu_write_buffer(fb, false); break;
            case REG_COMMAND_WRBUFSW: gpu_write_buffer(fb, true); break;
        }
            
        glt_reset_index();
    }
      
    return 0;
}


int gpu_capture_image(IMAGE *captured, BOOLEAN source)
{
    GLT glt_reg;
    u32 cmd, idx;
    
    if(fb <= 0) {
        ERROR("Framebuffer device is not ready!\n");
        return -1;
    }
    
    if(!captured)
        return -2;
    
    /* default settings */
    captured->visible = true;
    captured->tp = false;
    captured->hw_dep_prop.end_layer = true;
    captured->hw_dep_prop.command = REG_COMMAND_WRDISP;
    
    if(!captured->data)
        return -3;
        
    idx = glt_get_current_index();
    if(idx >= 0) {
        if(source)
            gpu_flush(idx, REG_COMMAND_WRDISP);
        else
            gpu_flush(idx, REG_COMMAND_WRBUF);
    }
        
    glt_index_inc();
    
    memset(&glt_reg, 0x0, sizeof(GLT));
    glt_reg.img_src_addr = (u32)captured->data;
    glt_reg.height = captured->height;
    glt_reg.width = captured->width;
    glt_reg.img_x = captured->x;
    glt_reg.img_y = captured->y;
    glt_reg.transparent = captured->tp;
    glt_reg.end_layer = captured->hw_dep_prop.end_layer;
    
    gpu_wait_busy(true);
    glt_write(glt_layer_idx_counter, &glt_reg);
      
    if(glt_reg.end_layer) {
        if(source)
            cmd = REG_COMMAND_CAPDISP;
        else
            cmd = REG_COMMAND_CAPBUF;
        ioctl(fb, FBIOCTL_SET_CMD, &cmd);
        gpu_wait_busy(true);
        glt_reset_index();
    }
    
    return 0;
}


void gpu_wait_busy(BOOLEAN polling)
{
    volatile u32 stat;
    
    stat = gpu_status(fb);
    if(stat & 0x01) {
        DPRINTF("GPU is busying...Wait!\n");
        if(polling) {
            for(;;) {
                stat = gpu_status(fb);
                if(!(stat & 0x01)) break;
            }
        }
        else {
            ioctl(fb, FBIOCTL_GPU_WAIT, 0);
        }
        DPRINTF("GPU free\n");
    }
}


inline void gpu_scale_image(IMAGE *image, s32 multiplier)
{
    int i = 0;
    
    /* Find i that satisfies '2^i >= multiplier' */
    for(i = 0; (0x1 << i) < multiplier; i++);
    image->hw_dep_prop.scale = i;
}


inline void gpu_scale_line(LINE *line, s32 multiplier)
{
    int i = 0;
    
    /* Find i that satisfies '2^i >= multiplier' */
    for(i = 0; (0x1 << i) < multiplier; i++);
    line->hw_dep_prop.scale = i;
}


inline void gpu_scale_rect(RECT *rect, s32 multiplier)
{
    int i = 0;
    
    /* Find i that satisfies '2^i >= multiplier' */
    for(i = 0; (0x1 << i) < multiplier; i++);
    rect->hw_dep_prop.scale = i;
}


inline u32 gpu_current_disp_addr(void)
{
    u32 addr;
    ioctl(fb, FBIOCTL_GET_DISP_ADDR, &addr);
    return addr;
}


inline u32 gpu_current_buf_addr(void)
{
    u32 addr;
    ioctl(fb, FBIOCTL_GET_BUF_ADDR, &addr);
    return addr;
}


inline void gpu_switch_page()
{
    u32 cmd = REG_COMMAND_SW;
    ioctl(fb, FBIOCTL_SET_CMD, &cmd);
}


inline u32 gpu_write_display(int fb)
{
    u32 cmd = REG_COMMAND_WRDISP;
    ioctl(fb, FBIOCTL_SET_CMD, &cmd);
    ioctl(fb, FBIOCTL_GET_CMD, &cmd);
    return cmd;
}


inline u32 gpu_write_buffer(int fb, BOOLEAN sw)
{
    u32 cmd;
    
    if(sw)
        cmd = REG_COMMAND_WRBUFSW;
    else
        cmd = REG_COMMAND_WRBUF;
        
    ioctl(fb, FBIOCTL_SET_CMD, &cmd);
    ioctl(fb, FBIOCTL_GET_CMD, &cmd);
    
    /* Syncronize buffer and display page if framebuffer switched */
    if(sw) {
        cmd = REG_COMMAND_WRBUF;
        gpu_wait_busy(true);
        ioctl(fb, FBIOCTL_SET_CMD, &cmd);
        return REG_COMMAND_WRBUFSW;
    }
        
    return cmd;
}


inline u32 gpu_status(int fb)
{
    u32 stat;
    ioctl(fb, FBIOCTL_GET_STATUS, &stat);
    return stat;
}


#endif /* GPU_SUPPORT */
